%%%-------------------------------------------------------------------
%%% File    : p42.erl
%%% Author  : Plamen Dragozov <plamen at dragozov.com>
%%% Description : 
%%% The n(th) term of the sequence of triangle numbers is given by, 
%%%   
%%% t_(n) = n(n+1)/2; 
%%% 
%%% so the first ten triangle numbers are:
%%%
%%% 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, ...
%%%
%%% By converting each letter in a word to a number corresponding to 
%%% its alphabetical position and adding these values we form a word 
%%% value. For example, the word value for SKY is 
%%% 19 + 11 + 25 = 55 = t_(10). 
%%% If the word value is a triangle number then we shall call the word 
%%% a triangle word.
%%%
%%% Using p42.txt, a 16K 
%%% text file containing nearly two-thousand common English words, how 
%%% many are triangle words?
%%%
%%%
%%% Created : 30 Dec 2008
%%%-------------------------------------------------------------------
-module(p42).

%% API
-compile(export_all).
% t(n) = n*(n+1)/2
% Find n from a known t(n):
% n*(n+1) = 2*t
% n**2 + n - 2*t = 0, solving a quadratic equation and taking only the positive solution.
% n1, 2 =  (-1 +/- sqrt(1+4*2*t))/2
% n > 0 ->
% n = (-1 + sqrt(1+8t))/2

%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function:  solution(File) -> Count
%% Description: Calculates the count of all triangle words (as defined in the problem) in a file.
%%--------------------------------------------------------------------
solution(File) ->
    {ok, Bin} = file:read_file(File),
    TWords = for_word(Bin, 0, 0, []),
    {length(TWords), TWords}.

%%====================================================================
%% Internal functions
%%====================================================================
%Iterate through all words in the file, and count them if they are found to be triangle.
for_word(Bin, Pos, Size, Acc) ->
  case Bin of
      <<_:Pos/binary, $",Word:Size/binary,$",_/binary>> ->
          NewAcc = case is_triangle(Word) of 
                       true -> [Word|Acc];
                       _ -> Acc
                   end,
          for_word(Bin, Pos + Size + 2, 0, NewAcc);
      <<_:Pos/binary, $", _:Size/binary, _/binary>>  ->
          for_word(Bin, Pos, Size + 1, Acc);
      <<_:Pos/binary, $,, _/binary>>  ->
          for_word(Bin, Pos + 1, 0, Acc);
      <<_:Pos/binary>>  ->
          Acc
end.

%check as defined above
is_triangle(Word) ->
    T = word_value(Word, 0),
    N = (-1 + math:sqrt(1 + 8 * T))/2,
    trunc(N)/1.0 =:= N.

%the word "value" as defined above
word_value(<<>>, Acc) -> Acc;
word_value(<<Char:8/integer,Rest/binary>>, Acc) ->
    word_value(Rest, Acc + a2i(Char)).

%Alphabet value of a character.
a2i(A) when A > $a ->
    A - $a + 1;
a2i(A) ->
    A - $A + 1.
